#pragma once
#include <Utility/Log.hpp>

//for base class of all zMatrix classes
//can choose row major or column major, column major is default

//#include "zSubMatrix.hpp"

namespace zzz{
class EndlessArray;
template<typename T, class Major>
class zSubMatrixR;
template<typename T, class Major>
class zSubMatrixW;


class zColMajor
{
public:
  static const zuint ToIndex(const zuint r,const zuint c, const zuint rows, const zuint cols)
  {return c*rows+r;}
  static void ToIndex(const zuint idx, const zuint rows, const zuint cols, zuint &r, zuint &c)
  {r=idx%rows; c=zuint(idx/rows);}
  static const int Dim_=1;
};

class zRowMajor
{
public:
  static const zuint ToIndex(zuint r,zuint c,zuint rows, zuint cols)
  {return r*cols+c;}
  static void ToIndex(const zuint idx, const zuint rows, const zuint cols, zuint &r, zuint &c)
  {c=idx%cols; r=zuint(idx/cols);}
  static const int Dim_=0;
};


//Readable Matrix
template<typename T, class Major>
class zMatrixBaseR
{
public:
  // constructor
  zMatrixBaseR(zuint r, zuint c):rows_(r),cols_(c){}
  // Check range
  void CheckRange(zuint r, zuint c) const {
    ZRCHECK_LT(r, rows_);
    ZRCHECK_LT(c, cols_);
  }
  void CheckRange(zuint idx) const {
    ZRCHECK_LT(idx, size());
  }
  // const operator() cannot be &, since may return some constant value calculated from input.
  virtual const T operator()(zuint r, zuint c) const=0;
  virtual const T operator[](zuint idx) const //might override to get faster
  {
    zuint r, c;
    ToIndex(idx, r, c);
    return (*this)(r,c);
  }
  // Submatrix
  zSubMatrixR<T,Major> operator()(const EndlessArray &r, const EndlessArray &c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }
  zSubMatrixR<T,Major> operator()(int r, const EndlessArray &c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }
  zSubMatrixR<T,Major> operator()(const EndlessArray &r, int c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }

  zSubMatrixR<T,Major> operator()(const zMatrixBaseR<int,Major> &r, const zMatrixBaseR<int,Major> &c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }
  zSubMatrixR<T,Major> operator()(int r, const zMatrixBaseR<int,Major> &c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }
  zSubMatrixR<T,Major> operator()(const zMatrixBaseR<int,Major> &r, int c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }

  zSubMatrixR<T,Major> operator()(const EndlessArray &r, const zMatrixBaseR<int,Major> &c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }
  zSubMatrixR<T,Major> operator()(const zMatrixBaseR<int,Major> &r, const EndlessArray &c) const
  {
    return zSubMatrixR<T,Major>(*this,r,c);
  }

  operator bool() const 
  {
    for (zuint r=0; r<rows_; r++) for (zuint c=0; c<cols_; c++)
      if (!((*this)(r,c))) return false;
    return true;
  }

  // According to Matlab, == is itemwise ==
  //operator==
//   bool operator==(const zMatrixBaseR<T,Major> &other) const {
//     if (rows_!=other.rows_ || cols_!=other.cols_) return false;
//     for (zuint r=0; r<rows_; r++) for (zuint c=0; c<cols_; c++)
//       if ((*this)(r,c) != other(r,c)) return false;
//     return true;
//   }
  //get size
  zuint Size(zuint i) const {
    ZCHECK(i==0 || i==1)<<"Size() accept only 0 or 1";
    if (i==0) return rows_;
    else if (i==1) return cols_;
    return -1;
  }
  zuint Rows() const {return rows_;}
  zuint Cols() const {return cols_;}
  zsize size() const {
    return rows_ * cols_;
  }
  //IO
  friend inline ostream& operator<<(ostream& os,const zMatrixBaseR<T,Major> &me) {
    for (zuint r=0; r<me.rows_; r++)  {
      for (zuint c=0; c<me.cols_; c++)
        os << me(r,c) << ' ';
      if (r!=me.rows_-1) os << '\n';
    }
    return os;
  }
  //the only member variable
  zuint rows_,cols_;
protected:
  inline const zuint ToIndex(const zuint r, const zuint c) const {
    return Major::ToIndex(r,c,zMatrixBaseR<T,Major>::rows_,zMatrixBaseR<T,Major>::cols_);
  }
  inline void ToIndex(const zuint idx, zuint &r, zuint &c) const {
    Major::ToIndex(idx,zMatrixBaseR<T,Major>::rows_,zMatrixBaseR<T,Major>::cols_,r,c);
  }
};

//Writable Matrix is also readable
template<typename T, class Major>
class zMatrixBaseW : public zMatrixBaseR<T,Major>
{
public:
  zMatrixBaseW(zuint r,zuint c):zMatrixBaseR<T,Major>(r,c){}
  // operator()
  virtual T& operator()(zuint r, zuint c)=0;
  // This function can be over ride to get faster performance
  virtual T& operator[](zuint idx)
  {
    zuint r, c;
    ToIndex(idx, r, c);
    return (*this)(r,c);
  }
  virtual const zMatrixBaseW<T,Major>& operator=(const zMatrixBaseR<T,Major> &other) {
    ZCHECK_EQ(rows_, other.rows_);
    ZCHECK_EQ(cols_, other.cols_);
    for (zuint i=0; i<rows_; i++) for (zuint j=0; j<cols_; j++)
      (*this)(i,j)=other(i,j);
    return *this;
  }

  // submatrix
  zSubMatrixW<T,Major> operator()(const EndlessArray &r, const EndlessArray &c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }
  zSubMatrixW<T,Major> operator()(int r, const EndlessArray &c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }
  zSubMatrixW<T,Major> operator()(const EndlessArray &r, int c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }

  zSubMatrixW<T,Major> operator()(const zMatrixBaseR<int,Major> &r, const zMatrixBaseR<int,Major> &c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }
  zSubMatrixW<T,Major> operator()(int r, const zMatrixBaseR<int,Major> &c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }
  zSubMatrixW<T,Major> operator()(const zMatrixBaseR<int,Major> &r, int c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }

  zSubMatrixW<T,Major> operator()(const EndlessArray &r, const zMatrixBaseR<int,Major> &c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }
  zSubMatrixW<T,Major> operator()(const zMatrixBaseR<int,Major> &r, const EndlessArray &c)
  {
    return zSubMatrixW<T,Major>(*this,r,c);
  }
  //IO
  friend inline istream& operator>>(istream& is,zMatrixBaseW<T,Major> &me) {
    for (zuint r=0; r<me.rows_; r++) for (zuint c=0; c<me.cols_; c++)
      is >> me(r,c);
    return is;
  }
  using zMatrixBaseR<T,Major>::operator();
  using zMatrixBaseR<T,Major>::ToIndex;
  using zMatrixBaseR<T,Major>::CheckRange;
  using zMatrixBaseR<T,Major>::rows_;
  using zMatrixBaseR<T,Major>::cols_;
};

}
